﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Core.Validation
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq.Expressions;
	using Falcon.Core.Extensions;

	public class Validator<T>
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="Validator&lt;T&gt;"/> class.
		/// </summary>
		public Validator()
		{
			this.Validators = new List<ValidationItem>();
			this.Result = new ValidationResult();
		}

		/// <summary>
		/// Gets or sets the validators.
		/// </summary>
		/// <value>The validators.</value>
		public IList<ValidationItem> Validators
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the result.
		/// </summary>
		/// <value>The result.</value>
		public ValidationResult Result
		{
			get;
			private set;
		}

		/// <summary>
		/// Adds the specified validation option.
		/// </summary>
		/// <param name="option">The option.</param>
		/// <returns></returns>
		public ValidationOption<T> Add(Expression<Func<T, object>> option)
		{
			var expression = (option.Body as MemberExpression);

			if (expression == null)
			{
				throw new ArgumentNullException("option");
			}

			return new ValidationOption<T>(expression.Member.Name, this.Validators);
		}

		/// <summary>
		/// Adds the specified error.
		/// </summary>
		/// <param name="bind">The bind.</param>
		/// <param name="message">The message.</param>
		public void AddError(string bind, string message)
		{
			this.Result.Add(new ValidationError
			{
				Bind = bind,
				Message = message
			});
		}

		/// <summary>
		/// Adds the specified error.
		/// </summary>
		/// <param name="option">The option.</param>
		/// <param name="message">The message.</param>
		public void AddError(Expression<Func<T, object>> option, string message)
		{
			var expression = (option.Body as MemberExpression);

			if (expression == null)
			{
				throw new ArgumentNullException("option");
			}

			this.Result.Add(new ValidationError
			{
				Bind = expression.Member.Name,
				Message = message
			});
		}

		/// <summary>
		/// Validates the specified instance.
		/// </summary>
		/// <param name="instance">The instance.</param>
		public void Validate(T instance)
		{
			foreach (var item in this.Validators)
			{
				if (!ProcessValidator(item, instance))
				{
					this.Result.Add(new ValidationError
					{
						Bind = item.Bind,
						Message = item.Error
					});
				}
			}

			if (this.Result.HasErrors)
			{
				throw new ValidationException(this.Result);
			}
		}

		/// <summary>
		/// Processes the validator.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="instance">The instance.</param>
		private static bool ProcessValidator(ValidationItem item, T instance)
		{
			var value = item.Bind.GetProperty(instance);

			object against;

			if (item.Against != null)
			{
				against = item.Against.GetProperty(instance);
			}
			else
			{
				try
				{
					against = Convert.ChangeType(item.Value, value.GetType(), CultureInfo.InvariantCulture);
				}
				catch
				{
					against = item.Value;
				}
			}
			
			return item.Validator.Validate(value, against);
		}
	}
}